# Clean Japan to US Exchange Data
exchange.jpy_to_usd_clean <- exchange.jpy_to_usd |>
rename(date = observation_date,
JPY = DEXJPUS) |>
# Fix Dates & Interpolate Missing Data
mutate(date = as.Date(date),
JPY = zoo::na.approx(JPY)) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Clean Euro to US Exchange Data
exchange.eur_to_usd_clean <- exchange.eur_to_usd |>
rename(date = observation_date,
EUR = DEXUSEU) |>
# Fix Dates & Interpolate Missing Data
mutate(date = as.Date(date),
EUR = zoo::na.approx(EUR),
EUR = 1/EUR) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Clean GBP to US Exchange Data
exchange.gbp_to_usd_clean <- exchange.gbp_to_usd |>
rename(date = observation_date,
GBP = DEXUSUK) |>
# Fix Dates & Interpolate Missing
mutate(date = as.Date(date),
GBP = zoo::na.approx(GBP),
GBP = 1/GBP) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Clean AUD to US Exchange Data
exchange.aud_to_usd_clean <- exchange.aud_to_usd |>
rename(date = observation_date,
AUD = DEXUSAL) |>
# Fix Dates & Interpolate Missing
mutate(date = as.Date(date),
AUD = zoo::na.approx(AUD),
AUD = 1/AUD) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Clean CAD to US Exchange Data
exchange.cad_to_usd_clean <- exchange.cad_to_usd |>
rename(date = observation_date,
CAD = DEXCAUS) |>
# Fix Dates & Interpolate Missing
mutate(date = as.Date(date),
CAD = zoo::na.approx(CAD)) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Clean CNY to US Exchange Data
exchange.cny_to_usd_clean <- exchange.cny_to_usd |>
rename(date = observation_date,
CNY = DEXCHUS) |>
# Fix Dates & Interpolate Missing
mutate(date = as.Date(date),
CNY = zoo::na.approx(CNY)) |>
# Filter To Last 25 Years
filter(date >= start_date) |>
arrange(date)
# Create Joint Exchane Data
exchange_df <- exchange.jpy_to_usd_clean |>
left_join(x = _, y = exchange.eur_to_usd_clean, by = 'date') |>
left_join(x = _, y = exchange.aud_to_usd_clean, by = 'date') |>
left_join(x = _, y = exchange.gbp_to_usd_clean, by = 'date') |>
left_join(x = _, y = exchange.cad_to_usd_clean, by = 'date') |>
left_join(x = _, y = exchange.cny_to_usd_clean, by = 'date') |>
arrange(date)
# Clear Original Data
rm(exchange.aud_to_usd, exchange.cad_to_usd, exchange.eur_to_usd,
exchange.jpy_to_usd, exchange.gbp_to_usd, exchange.cny_to_usd)Capstone - Exchange Rate Analysis
Import Exchange Rates
All exchange rates were obtained from FRED, the Federal Reserve of St. Louis Economic Data database.
Exchange rates were downloaded for the following currencies:
Japan Yen to USD
USD to Euro
US to British Pound
USD to Australian Dollar
Canadian Dollar to USD
Chinese Yuan to UDS
All data will be converted as needed to Foreign Currency to USD for consistency in data analysis.
Source: Federal Reserve Bank of St. Louis
Exchange Rate Plots
Below is 25-years of historical exchange rates. The Japan/China exchange rates have been scaled to fit into one plot with the western currencies.
You can also see in the plot that the Chinese Yuan was pegged to the U.S. Dollar until roughly 2005.
Analyze Exchange Rates
Our analysis of the exchange rates will seek to find
Historical Daily Log Returns
Historical Daily Volatility
Historical Annualized Volatility
Correlation Between Currencies Daily Returns
Measure Volatility
# Calculate Daily Log Returns
daily_returns <- exchange_df |>
pivot_longer(cols = !starts_with('date'), names_to = 'currency', values_to = 'rate') |>
arrange(currency, date) |>
group_by(currency) |>
mutate(daily_return = log(rate) - log(lag(rate, n = 1))) |>
ungroup() |>
drop_na(daily_return)
# Calculate Daily Volatility
daily_volatility <- daily_returns |>
group_by(currency) |>
summarise(daily_volatility = sd(daily_return, na.rm = TRUE)) |>
ungroup()
# Convert To Annualize Volatility
annual_volatility <- daily_volatility |>
mutate(annual_volatility = daily_volatility*sqrt(252))| Currency to USD Volatility | ||
|---|---|---|
| Currency | Daily Volatility | Annualized Volatility |
| AUD | 0.75% | 11.94% |
| CAD | 0.52% | 8.30% |
| CNY | 0.17% | 2.74% |
| EUR | 0.56% | 8.90% |
| GBP | 0.57% | 9.08% |
| JPY | 0.60% | 9.54% |
Correlation
The correlation matrix constructed in this analysis will be used to compute the correlated price patterns later using Geometric Brownian Motion.
As we can see from the correlation matrix and plots, the Western currencies (AUD, CAD, EUR, GBP) tend to be highly correlated with each other in their movements against the U.S. Dollar, while the Asian currencies exhibit very little correlation.
# Create Correlation Matrix
daily_return_matrix <- daily_returns |>
select(date, currency, daily_return) |>
pivot_wider(names_from = currency, values_from = daily_return) |>
select(-date) |>
cor()| Correlation Matrix | ||||||
|---|---|---|---|---|---|---|
| AUD | CAD | CNY | EUR | GBP | JPY | |
| AUD | 1.0000 | 0.6511 | 0.2289 | 0.5727 | 0.5539 | 0.1161 |
| CAD | 0.6511 | 1.0000 | 0.1750 | 0.4795 | 0.4705 | 0.0583 |
| CNY | 0.2289 | 0.1750 | 1.0000 | 0.2138 | 0.2322 | 0.1291 |
| EUR | 0.5727 | 0.4795 | 0.2138 | 1.0000 | 0.6484 | 0.3216 |
| GBP | 0.5539 | 0.4705 | 0.2322 | 0.6484 | 1.0000 | 0.1890 |
| JPY | 0.1161 | 0.0583 | 0.1291 | 0.3216 | 0.1890 | 1.0000 |
Daily Return Distribution
Theoretically returns should be from a normal distribution to use Geometric Brownian Motion. We can see below that the returns are not normal, but for a simplistic model we’ll use GBM as has been done in several studies modeling exchange rates.
Historical Paths
Below we’ll examine 25-years of historical price paths to serve as a baseline indicator of how volatile exchange rates have been historically.
This will serve as a useful benchmark when we forecast possible exchange rate paths using GBM.
# Calculate Historical Paths Wide
hist_paths <- exchange_df |>
mutate(YEAR = lubridate::year(date)) |>
group_by(YEAR) |>
arrange(date) |>
mutate(INDEX = row_number()) |>
ungroup() |>
arrange(YEAR, INDEX) |>
pivot_longer(cols = c('JPY','EUR','AUD','GBP','CAD','CNY'), names_to = 'COUNTRY', values_to = 'RATE') |>
select(-c(date)) |>
pivot_wider(names_from = YEAR, values_from = RATE) |>
arrange(COUNTRY, INDEX)
# Create Country Paths Long
hist_paths_long <- exchange_df |>
mutate(YEAR = lubridate::year(date)) |>
group_by(YEAR) |>
arrange(date) |>
mutate(INDEX = row_number()) |>
ungroup() |>
arrange(YEAR, INDEX) |>
pivot_longer(cols = c('JPY','EUR','AUD','GBP','CAD','CNY'), names_to = 'COUNTRY', values_to = 'RATE') |>
select(-c(date)) |>
arrange(COUNTRY, YEAR, INDEX)
# Create Adjusted Country Paths
hist_paths_adj <- hist_paths_long |>
group_by(YEAR, COUNTRY) |>
mutate(STARTING_RATE = first(RATE)) |>
ungroup() |>
mutate(RATE = RATE - STARTING_RATE)
# Create Adjusted Percentage Paths
hist_paths_pct <- hist_paths_long |>
group_by(YEAR, COUNTRY) |>
mutate(STARTING_RATE = first(RATE)) |>
ungroup() |>
mutate(RATE = (RATE - STARTING_RATE)/STARTING_RATE)
# Calculate Summary States
hist_path_stats <- hist_paths_pct |>
group_by(YEAR, COUNTRY) |>
filter(INDEX == max(INDEX)) |>
ungroup() |>
# Calculate Rankings
group_by(COUNTRY) |>
arrange(RATE) |>
mutate(RANK = row_number()) |>
ungroup() |>
mutate(BOTTOM_25 = if_else(RANK <= 6, RATE, NA_real_),
TOP_25 = if_else(RANK >= 19, RATE, NA_real_ )) |>
arrange(COUNTRY) |>
group_by(COUNTRY) |>
summarise(MIN = min(RATE, na.rm = TRUE),
MAX = max(RATE, na.rm = TRUE),
AVG = mean(RATE, na.rm = TRUE),
MIN_25TH = mean(BOTTOM_25, na.rm = TRUE),
TOP_25TH = mean(TOP_25, na.rm = TRUE)) |>
ungroup()Plots
Table
| Foreign Exchange Historical Paths Statistics | |||||
|---|---|---|---|---|---|
| Country | Min | Max | Avg | Min 25% | Top 25% |
| AUD | −25.17% | 25.93% | 0.27% | −14.23% | 12.51% |
| CAD | −18.08% | 23.62% | 0.13% | −10.95% | 11.10% |
| CNY | −6.53% | 8.53% | −0.40% | −5.59% | 4.33% |
| EUR | −17.26% | 13.80% | −0.59% | −12.19% | 9.03% |
| GBP | −11.69% | 35.67% | 0.73% | −9.58% | 12.38% |
| JPY | −17.99% | 21.16% | 1.65% | −10.96% | 14.30% |
Simulate Exchange Rate Paths
Below we’ll simulate the possible exchange rate paths for all six currencies using correlated Geometric Brownian Motion.
The formula for Geometric Brownian Motion is shown below:’
\(S_t = S_0 * exp((\mu - \frac12\sigma^2)t+\sigma W_t)\)
- term
-
\(S_t\) = Asset Price at Time \(t\)
\(S_0\) = Initial Price of Asset
\(\mu\) = Drift Term (growth rate of asset path)
\(\sigma\) = Volatility
\(W_t\) = Standard Brownian Motion, or random variable from standard normal distribution.
Assumptions
For each country we’ll use their historical daily volalitility as \(\sigma\), and we’ll assume a zero drift rate for all currencies. Financial theory dictates that the differences in each countries respective risk free rate vs. the domestic currency may be used as the drift rate to indicate the cost of holding the one currency compared to the other. For this analysis we won’t be incorporating the risk free rates of return.
GBM Functions
GBM <- function(N, sigma, mu, S0, Wt = NULL) {
# Creates a single asset path of daily prices using Geometric Brownian Motion.
# One year is 252 days since that is about how many trading days are in any
# given year.
#
# Args:
# N: Number of days in the path.
# sigma: Volatility or standard deviation of daily continuously compounded
# returns.
# mu: Drift or average daily continuously compounded returns.
# S0: The initial price of the asset.
# Wt: The cumulative Brownian motion of the model. This can be supplied or
# left as NULL. In the case that it is NULL, a vector will be provided.
# If you include this argument, it must be a vector of length N of the
# cumulative sum of a random variable to work properly.
#
# Returns:
# A vector of length N containing the asset prices generated by the specified
# GBM.
if (is.null(Wt)) {
Wt <- cumsum(rnorm(N, 0, 1))
}
t <- (1:N)/252
p1 <- (mu - 0.5*(sigma*sigma)) * t
p2 <- sigma * Wt
St = S0 * exp(p1 + p2)
return(St)
}
CorrelatedGBM <- function(N, S0, mu, sigma, cor.mat) {
# Creates a matrix of correlated daily price paths using Geometric
# Brownian Motion.
#
# Args:
# N: Number of days in the path.
# mu: Drift or average daily continuously compounded returns.
# sigma: Volatility or standard deviation of daily continuously compounded
# returns.
# S0: The initial price of the asset.
# cor.mat: The correlation matrix of the daility contiuously compounded
# returns.
#
# Returns:
# A matrix of simulated daily price paths of length N having the same number
# of assets as in the mu and sigma vectors. Note that mu and sigma must have
# the same dimensions.
mu <- as.matrix(mu)
sigma <- as.matrix(sigma)
GBMs <- matrix(nrow = N, ncol = nrow(mu))
Wt <- matrix(rnorm(N * nrow(mu), 0, 1), ncol = nrow(mu))
Wt <- apply(Wt, 2, cumsum)
chol.mat <- chol(cor.mat) # upper triangular cholesky decomposition
Wt <- Wt %*% chol.mat # key trick for creating correlated paths
for (i in 1:nrow(mu)) {
GBMs[,i] <- GBM(N, sigma[i], mu[i] , S0[i], Wt[, i])
}
return(GBMs)
}Compute Simulated GBM Paths
# Parameters
set.seed (1)
hundreds <- 5
sims <- 100*hundreds
# Create Sim Path Aggregate
sim_path_agg <- data.frame()
# Create Simulated Exchange Rate Paths
for (s in 1:sims){
# Create Correlated Path
temp_corr_gbm <- CorrelatedGBM(N = 252,
S0 = joint_data$current_rate,
mu = rep(0,6),
sigma = joint_data$daily_volatility,
cor.mat = daily_return_matrix)
# Clean Path Data
temp_corr_gbm_df <- temp_corr_gbm |>
as.data.frame() |>
rename(!!joint_data$currency[1] := V1,
!!joint_data$currency[2] := V2,
!!joint_data$currency[3] := V3,
!!joint_data$currency[4] := V4,
!!joint_data$currency[5] := V5,
!!joint_data$currency[6] := V6) |>
mutate(sim = s,
days = row_number()) |>
relocate(c(days,sim), .before = everything()) |>
arrange(days)
# Add To Aggregate
sim_path_agg <- rbind(sim_path_agg,
temp_corr_gbm_df)
# Kill Variablesa
rm(temp_corr_gbm_df,temp_corr_gbm)
}Compute Simulated Correlation
We’ll want to confirm the correlation of daily log returns of the simulated paths are roughly similar to historical return correlation.
# Compute Returns
sim_path_agg_returns <- sim_path_agg |>
group_by(sim) |>
mutate(AUD_RETURNS = log(AUD) - log(lag(AUD, n = 1)),
CAD_RETURNS = log(CAD) - log(lag(CAD, n = 1)),
CNY_RETURNS = log(CNY) - log(lag(CNY, n = 1)),
EUR_RETURNS = log(EUR) - log(lag(EUR, n = 1)),
GBP_RETURNS = log(GBP) - log(lag(GBP, n = 1)),
JPY_RETURNS = log(JPY) - log(lag(JPY, n = 1))) |>
ungroup() |>
drop_na() |>
select(days, sim, contains('RETURNS')) |>
arrange(sim, days)
# Calcualte Correlation
sim_path_agg_returns_cor <- sim_path_agg_returns |>
select(contains('RETURNS')) |>
cor()
# Reset Rownames
rownames(sim_path_agg_returns_cor) <- joint_data$currency| Simulated Exchange Path Correlations | ||||||
|---|---|---|---|---|---|---|
| AUD | CAD | CNY | EUR | GBP | JPY | |
| AUD | 1.00000 | 0.64991 | 0.23068 | 0.57392 | 0.55366 | 0.11788 |
| CAD | 0.64991 | 1.00000 | 0.17470 | 0.48018 | 0.46853 | 0.05773 |
| CNY | 0.23068 | 0.17470 | 1.00000 | 0.21404 | 0.23288 | 0.13188 |
| EUR | 0.57392 | 0.48018 | 0.21404 | 1.00000 | 0.65051 | 0.32475 |
| GBP | 0.55366 | 0.46853 | 0.23288 | 0.65051 | 1.00000 | 0.19191 |
| JPY | 0.11788 | 0.05773 | 0.13188 | 0.32475 | 0.19191 | 1.00000 |
Analyze Exchange Rate Paths
Below are the simulated GBM paths for each currency exchange rate. Included are historical yearly changes to serve as a comparison for actual changes.
You can see that the majority of the simulated paths fit mostly within historical annual changes.
Export Exchange Rate Paths
# Export Exchange Rates As RDS
sim_path_agg |>
saveRDS(object = _,
file = paste0('C:/Users/bmdah/OneDrive/Desktop/Classes/',
'SCU_Classes/Capstone/project/modeling/correlated_simulated_exchange_paths.rds'))